Skip to content

docs: offchain canister calls guide#92

Merged
marc0olo merged 2 commits into
mainfrom
docs/guides-canister-calls-offchain
Apr 16, 2026
Merged

docs: offchain canister calls guide#92
marc0olo merged 2 commits into
mainfrom
docs/guides-canister-calls-offchain

Conversation

@marc0olo
Copy link
Copy Markdown
Member

Summary

  • Agent protocol: CBOR encoding, request IDs, certificate verification, update call polling
  • Query vs update comparison table
  • @icp-sdk/core (JS/TS): binding generation with @icp-sdk/bindgen, browser and Node.js actors
  • ic-agent (Rust): agent init, query/update calls with Encode!/Decode!, identity types
  • Canister discovery via ic_env cookie and PUBLIC_CANISTER_ID env vars
  • Community agents: Go, Java/Android, Dart/Flutter, .NET, Elixir, C
  • Authentication: anonymous vs authenticated, Internet Identity integration

Sync recommendation

informed by dfinity/icp-cli — docs/concepts/canister-discovery.md; dfinity/icp-js-sdk-docs — public/core/latest.zip

@marc0olo
Copy link
Copy Markdown
Member Author

Review: Offchain Calls

Must fix

  • Misleading query response verification claim: The "Query vs update calls" table says query response verification is "Optional (certified queries available)." This is misleading. HttpAgent enables verifyQuerySignatures by default (true per HttpAgentOptions), which verifies node key signatures on every query response. Saying "optional" implies queries are unverified by default — the opposite of SDK behaviour. Suggested fix: change the cell to "Node key signatures verified by default; certified data provides app-layer guarantees" or simply "Verified by default (node key signatures)".

  • Inconsistent CanisterEnv pattern across two sections: The "Creating an actor (browser)" section (lines 93–113) defines a local interface CanisterEnv and passes it as a generic (getCanisterEnv<CanisterEnv>()). The "Canister discovery — Frontend: reading the cookie" section (lines 259–272) instead shows the declare module augmentation pattern. The canonical template source (hello-world/frontend/app/src/App.tsx) uses the local interface + generic approach. Having two different patterns for the same API in one page will confuse readers. Pick one and apply it consistently; the local interface + generic matches the template.

  • rootKey comment on mainnet is misleading: Line 110 says // On mainnet, pass the root key from the cookie for certificate verification. The IC root key is already embedded in the agent for mainnet — developers should NOT override it with a cookie-supplied key on mainnet. Passing a custom rootKey is only needed for non-standard networks. Suggest: // Pass rootKey only on non-standard networks. On mainnet the IC root key is embedded in the agent; omit rootKey there.

Suggestions

  • icp network start comment in vite.config example: Line 283 comments // from 'icp network start' output. The icp network start command does not print the root key per the CLI reference. The local-development guide describes fetching canister IDs and root key from the CLI dynamically at dev-server startup and points to the frontend-environment-variables example. The hello-world template hardcodes the root key as a placeholder. Suggest changing to: // placeholder — replace with your local replica root key and pointing to the icp-cli-templates frontend-environment-variables example.

  • Node.js mainnet snippet lacks a comment on why shouldFetchRootKey is absent: The Node.js section shows shouldFetchRootKey: true in the local dev variant but no explanation on the mainnet variant. A short comment // IC root key is embedded in the agent for mainnet — do not set shouldFetchRootKey on the mainnet snippet would prevent confusion about the asymmetry.

  • Rust update call: clarify .call() vs .call_and_wait(): The page says "use .call_and_wait() instead of .call()" for update calls but doesn't explain why .call() alone isn't sufficient for updates. A brief inline comment // call_and_wait() submits the update and polls until the response is certified would help developers understand the polling step.

Verified

  • All four internal links verified with ls: candid.mdx, onchain-calls.mdx, internet-identity.mdx, asset-canister.md all exist. Astro resolves .md links to .mdx files — no broken links.
  • getCanisterEnv function signature, import path @icp-sdk/core/agent/canister-env, and CanisterEnv.IC_ROOT_KEY: Uint8Array verified against icp-js-sdk-docs/public/core/latest.zip (libs/agent/canister-env/api/).
  • HttpAgent.create static async method and HttpAgentOptions fields (host, identity, rootKey, shouldFetchRootKey) verified against icp-js-sdk-docs/public/core/latest.zip (libs/agent/api/classes/HttpAgent.md, libs/agent/api/interfaces/HttpAgentOptions.md).
  • createActor(canisterId, { agentOptions: {...} }) signature verified against icp-js-sdk-docs/public/bindgen/latest.zip (structure.md) — CreateActorOptions has agent?, agentOptions?: HttpAgentOptions, actorOptions?. Usage in the page is correct.
  • npx @icp-sdk/bindgen --did-file ... --out-dir ... verified against bindgen CLI docs (cli/index.md). Correct "without installation" form.
  • vite.config cookie pattern (ic_root_key=...&PUBLIC_CANISTER_ID:backend=...) and Set-Cookie header approach verified against icp-cli-templates/hello-world/frontend/app/vite.config.ts.
  • Browser actor code pattern (local interface CanisterEnv, generic getCanisterEnv<CanisterEnv>(), createActor with agentOptions) verified against icp-cli-templates/hello-world/frontend/app/src/App.tsx.
  • PUBLIC_CANISTER_ID:<canister-name> injection and ic_env cookie mechanism verified against .sources/icp-cli/docs/concepts/canister-discovery.md.
  • icp canister list command verified against .sources/icp-cli/docs/reference/cli.md. Valid.
  • Frontmatter complete and consistent (title, description, sidebar.order).
  • No dfx references, no mo:base imports, no JSX in .md file, no banned external URLs.
  • <\!-- Upstream: --> comment present at end of page as required.
  • Anonymous principal 2vxsx-fae is correct per IC interface spec.
  • Rust identity types (AnonymousIdentity, BasicIdentity, Secp256k1Identity, Prime256v1Identity) and docs.rs/ic-agent URL are correct.

- Fix misleading "Optional" query response verification cell — HttpAgent
  verifies node key signatures by default (verifyQuerySignatures: true)
- Unify CanisterEnv pattern: use local interface + generic throughout,
  replacing the declare module augmentation in the Canister discovery
  section; matches the icp-cli-templates hello-world template
- Fix misleading rootKey comment: IC root key is embedded in the agent
  for mainnet; rootKey override is for non-standard networks only
- Fix vite.config root key comment: change to 'placeholder' wording
  to avoid implying icp network start prints it (not in CLI reference)
- Add Node.js mainnet comment: explain why shouldFetchRootKey is absent
- Add Rust call_and_wait comment: clarify it polls until response is certified
@marc0olo
Copy link
Copy Markdown
Member Author

<!-- feedback-addressed -->

Feedback addressed

Changes applied

Must fix — Query response verification table cell

  • Changed "Optional (certified queries available)" to "Node key signatures verified by default; certified data provides app-layer guarantees"
  • Verified against icp-js-sdk-docs/public/core/latest.zip (libs/agent/api/interfaces/HttpAgentOptions.md): verifyQuerySignatures defaults to true

Must fix — Inconsistent CanisterEnv pattern

  • The "Canister discovery — Frontend: reading the cookie" section used a declare module augmentation pattern, while "Creating an actor (browser)" used local interface + generic
  • Unified to local interface + generic throughout; this matches the icp-cli-templates/hello-world/frontend/app/src/App.tsx canonical template
  • Both sections now use: interface CanisterEnv { ... } and getCanisterEnv<CanisterEnv>()

Must fix — Misleading rootKey comment on mainnet

  • Line 110 previously said // On mainnet, pass the root key from the cookie for certificate verification. — this is incorrect since the IC root key is embedded in the agent for mainnet and should not be overridden
  • Changed to: // Pass rootKey only on non-standard networks. On mainnet the IC root key is embedded in the agent — omit rootKey there.

Suggestion — vite.config root key comment

  • Changed // from 'icp network start' output to // placeholder — replace with your local replica root key
  • The icp network start CLI reference does not describe printing a root key; "placeholder" is safer and avoids a misleading implication

Suggestion — Node.js mainnet snippet

  • Added inline comment to the mainnet HttpAgent.create call: // IC root key is embedded in the agent for mainnet — do not set shouldFetchRootKey.
  • This explains the asymmetry with the local dev snippet that uses shouldFetchRootKey: true

Suggestion — Rust call_and_wait clarification

  • Added inline comment to .call_and_wait(): // submits the update and polls until the response is certified
  • Explains why .call() alone is not sufficient for update calls

Items skipped

None. All feedback items were verified against .sources/ and applied.

@marc0olo marc0olo merged commit 9f91093 into main Apr 16, 2026
1 check passed
@marc0olo marc0olo deleted the docs/guides-canister-calls-offchain branch April 16, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant